﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using Creventive.Wpf.ShellFactory.Utility;

namespace Creventive.Wpf.ShellFactory.Workspace.Primitives
{
    /// <summary>
    ///   Represents a general base class for workspaces which should be able to handle multiple views.
    /// </summary>
    public abstract class MultiViewWorkspaceBase : Workspace, IMultiViewWorkspace
    {
        #region Static Fields/Constants

        /// <summary>
        /// Provides the backing field for the dependency property Source.
        /// </summary>
        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(IEnumerable), typeof(MultiViewWorkspaceBase), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, (s, e) => ((MultiViewWorkspaceBase)s).OnSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue)));

        private static readonly DependencyPropertyKey ItemsPropertyKey = DependencyProperty.RegisterReadOnly("Items", typeof(ReadOnlyObservableCollection<WorkspaceItem>), typeof(Workspace), new UIPropertyMetadata(null));

        /// <summary>
        /// Provides the backing field for the dependency property Items.
        /// </summary>
        public static readonly DependencyProperty ItemsProperty = ItemsPropertyKey.DependencyProperty;
        private static readonly DependencyPropertyKey ActiveWorkspaceItemPropertyKey = DependencyProperty.RegisterReadOnly("ActiveWorkspaceItem", typeof(WorkspaceItem), typeof(MultiViewWorkspaceBase), new UIPropertyMetadata(null, (s, e) => ((MultiViewWorkspaceBase)s).OnActiveWorkspaceItemChanged((WorkspaceItem)e.OldValue, (WorkspaceItem)e.NewValue)));

        /// <summary>
        /// Provides the backing field for the dependency property ActiveWorkspaceItem.
        /// </summary>
        public static readonly DependencyProperty ActiveWorkspaceItemProperty = ActiveWorkspaceItemPropertyKey.DependencyProperty;

        #endregion

        private ItemsSourceCollection<IApplicationViewSource> viewSources;
        private IItemsSourceHandler viewSourcesHandler;
        private ObservableCollection<WorkspaceItem> items;

        /// <summary>
        ///   Initializes a new instance of the <see cref = "MultiViewWorkspaceBase" /> class.
        /// </summary>
        protected MultiViewWorkspaceBase()
        {
            this.items = new ObservableCollection<WorkspaceItem>();
            this.SetValue(ItemsPropertyKey, new ReadOnlyObservableCollection<WorkspaceItem>(this.items));
            ItemsSourceCollection<IApplicationViewSource>.Create(out this.viewSources, out this.viewSourcesHandler);
            this.ViewSources.CollectionChanged += this.ViewSources_CollectionChanged;
        }

        /// <summary>
        ///   Gets the currently active workspace item. You will find the view in its Content property.
        /// </summary>
        /// <value>The active workspace item.</value>
        public WorkspaceItem ActiveWorkspaceItem
        {
            get { return (WorkspaceItem)this.GetValue(ActiveWorkspaceItemProperty); }
            protected set { this.SetValue(ActiveWorkspaceItemPropertyKey, value); }
        }

        /// <summary>
        ///   Gets a list of the workspace items in this workspace.
        /// </summary>
        /// <value>The loaded items.</value>
        public ReadOnlyObservableCollection<WorkspaceItem> Items
        {
            get { return (ReadOnlyObservableCollection<WorkspaceItem>)this.GetValue(ItemsProperty); }
        }

        /// <summary>
        ///   Gets an enumerator for logical child elements of this element.
        /// </summary>
        /// <value></value>
        /// <returns>An enumerator for logical child elements of this element.</returns>
        protected override IEnumerator LogicalChildren
        {
            get { return this.Items.GetEnumerator(); }
        }

        /// <summary>
        ///   Gets or sets the source of the view sources to use in this workspace. Do not mix with direct content.
        /// </summary>
        /// <value>The source.</value>
        public IEnumerable Source
        {
            get { return (IEnumerable)this.GetValue(SourceProperty); }
            set { this.SetValue(SourceProperty, value); }
        }

        /// <summary>
        ///   Gets the list of view sources currently added to this view.
        /// </summary>
        /// <value>The view sources.</value>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ItemsSourceCollection<IApplicationViewSource> ViewSources
        {
            get { return this.viewSources; }
        }

        /// <summary>
        ///   Called when the active workspace item has changed.
        /// </summary>
        /// <param name = "oldItem">The old item.</param>
        /// <param name = "newItem">The new item.</param>
        protected virtual void OnActiveWorkspaceItemChanged(WorkspaceItem oldItem, WorkspaceItem newItem)
        {
            if (oldItem == newItem)
                return;

            if (oldItem != null)
                WorkspaceHelper.NotifyDeactivated(oldItem);

            if (newItem != null)
            {
                this.AddLogicalChild(newItem);
                WorkspaceHelper.NotifyActivated(newItem);
            }
        }

        private void ViewSources_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    this.Open((IApplicationViewSource)e.NewItems[0]);
                    break;

                case NotifyCollectionChangedAction.Move:
                    throw new NotImplementedException();

                case NotifyCollectionChangedAction.Remove:
                    this.Close((IApplicationViewSource)e.OldItems[0]);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    this.Close((IApplicationViewSource)e.OldItems[0]);
                    this.Open((IApplicationViewSource)e.NewItems[0]);
                    break;

                case NotifyCollectionChangedAction.Reset:
                {
                    this.CloseAll(ApplicationViewClosingMode.NoQuery);
                    ((IEnumerable<IApplicationViewSource>)sender).ForEach(this.Open);
                    break;
                }
            }
        }

        /// <summary>
        ///   Called when a new view has been added to the workspace.
        /// </summary>
        /// <param name = "view">The view.</param>
        protected override sealed void OnAddView(ApplicationView view)
        {
            this.OnAddView(view, this.Items.Count);
        }

        /// <summary>
        ///   Clears the active workspace item property.
        /// </summary>
        protected void ClearActiveWorkspaceItem()
        {
            this.ClearValue(ActiveWorkspaceItemPropertyKey);
        }

        /// <summary>
        ///   Called when a view should be moved.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <param name = "sourceIndex">The original index of the view.</param>
        /// <param name = "targetIndex">Where to place the view.</param>
        protected abstract void OnMoveView(ApplicationView view, int sourceIndex, int targetIndex);

        /// <summary>
        ///   Called when a new view has been added to the workspace.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <param name = "targetIndex">Where to place the new view.</param>
        protected abstract void OnAddView(ApplicationView view, int targetIndex);

        /// <summary>
        ///   Called when the Source property has changed.
        /// </summary>
        /// <param name = "oldValue">The old value.</param>
        /// <param name = "newValue">The new value.</param>
        protected virtual void OnSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            if (oldValue == newValue)
                return;
            this.viewSourcesHandler.SetSource(newValue);
        }

        /// <summary>
        ///   Determines whether the specified application view exists within this workspace.
        /// </summary>
        /// <param name = "applicationView">The application view.</param>
        /// <returns>
        ///   <c>true</c> if the specified application view contains view; otherwise, <c>false</c>.
        /// </returns>
        public override bool ContainsView(ApplicationView applicationView)
        {
            return this.items.Any(i => ReferenceEquals(i.ApplicationView, applicationView));
        }

        /// <summary>
        ///   Creates and initializes a workspace item for a given view.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <returns></returns>
        protected override WorkspaceItem CreateItem(ApplicationView view)
        {
            var item = base.CreateItem(view);
            this.items.Add(item);
            return item;
        }

        /// <summary>
        ///   Initializes the given item using the given view.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <param name = "view">The view.</param>
        protected override void InitializeItem(WorkspaceItem item, ApplicationView view)
        {
            base.InitializeItem(item, view);
            this.AddLogicalChild(item);
        }

        /// <summary>
        ///   Destroys the given workspace item.
        /// </summary>
        /// <param name = "item">The item.</param>
        protected override void DestroyItem(WorkspaceItem item)
        {
            this.items.Remove(item);
            this.RemoveLogicalChild(item);
            base.DestroyItem(item);
        }

        /// <summary>
        ///   Gets the workspace item for the given view. Returns null if no workspace item has been created.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <param name = "preferredIndex">The preferred targetIndex of the item Index of the preferred.</param>
        /// <param name = "flags">Behavior modification flags.</param>
        /// <returns></returns>
        protected WorkspaceItem GetItemForView(ApplicationView view, int preferredIndex = -1, GetItemsFlags flags = GetItemsFlags.Default)
        {
            var item = this.items.SingleOrDefault(i => i.ApplicationView == view);
            if (item == null && flags == GetItemsFlags.LoadIfMissing)
                item = this.CreateItem(view);
            return item;
        }

        /// <summary>
        ///   Called when the ContainerStyle property has changed.
        /// </summary>
        /// <param name = "oldValue">The old value.</param>
        /// <param name = "newValue">The new value.</param>
        protected override void OnContainerStyleChanged(Style oldValue, Style newValue)
        {
            if (oldValue == newValue)
                return;

            if (oldValue != null && newValue == null)
                this.Items.ForEach(i => i.ClearValue(StyleProperty));
            else
                this.Items.ForEach(i => i.Style = newValue);
        }

        #region Nested type: GetItemsFlags

        /// <summary>
        ///   Flags for the GetItemForView method
        /// </summary>
        protected enum GetItemsFlags
        {
            /// <summary>
            ///   Default behavior
            /// </summary>
            Default,

            /// <summary>
            ///   If the requested item does not exist, load it
            /// </summary>
            LoadIfMissing
        }

        #endregion

        #region IMultiViewWorkspace Members

        IEnumerable<WorkspaceItem> IMultiViewWorkspace.Items
        {
            get { return items; }
        }

        #endregion
    }
}